/**
 * @dev Information about the user's wallet, network, etc. are stored and managed here
 *
 * @dev State is handled in reusable components, where each component is its own self-contained file consisting of
 * one function defined used the composition API. Since we want the wallet state to be shared between all instances
 * when this file is imported, we defined state outside of the function definition.
 *
 * @dev When assigning ethers objects as refs, we must wrap the object in `markRaw` for assignment. This is not required
 * with Vue 2's reactivity system based on Object.defineProperty, but is required for Vue 3's reactivity system based
 * on ES6 proxies. The Vue 3 reactivity system does not work well with non-configurable, non-writable properties on
 * objects, and many ethers classes, such as providers and networks, use non-configurable or non-writable properties.
 * Therefore we wrap the object in `markRaw` to prevent it from being converted to a Proxy. If you do not do this,
 * you'll see errors like this when using ethers objects as refs:
 *     Uncaught (in promise) TypeError: 'get' on proxy: property '_network' is a read-only and non-configurable data
 *     property on the proxy target but the proxy did not return its actual value (expected '#<Object>' but got
 *     '[object Object]')
 * Read more here:
 *     - https://stackoverflow.com/questions/65693108/threejs-component-working-in-vuejs-2-but-not-3
 *     - https://github.com/vuejs/vue-next/issues/3024
 *     - https://v3.vuejs.org/api/basic-reactivity.html#markraw
 */

import {computed, markRaw, ref} from 'vue';
import useContractHook from '@/hooks/Contract';
import {JsonRpcProvider, Web3Provider} from '@/utils/ethers';
import {formatAddress} from '@/utils/formatters';

// import injectedModule from '@web3-onboard/injected-wallets';
// // import coinbaseModule from '@web3-onboard/coinbase';
// import walletConnectModule from '@web3-onboard/walletconnect'
// import torusModule from '@web3-onboard/torus'
// // import keepkeyModule from '@web3-onboard/keepkey'
// import ledgerModule from '@web3-onboard/ledger'
// // import fortmaticModule from '@web3-onboard/fortmatic'
// import mewModule from '@web3-onboard/mew'
// // import portisModule from '@web3-onboard/portis'
// import keystoneModule from '@web3-onboard/keystone'
// import dcentModule from '@web3-onboard/dcent'
// import gnosisModule from '@web3-onboard/gnosis'
//
// import Onboard from "@web3-onboard/core"
// import {init, useOnboard} from '@web3-onboard/vue'
import {ethers} from "ethers";

const {startPolling} = useContractHook();
// const defaultProvider = new JsonRpcProvider(ETH_RPC_URL);

import Dialog from "@/components/dialog/dialog"
import {initializeWeb3Model} from "@/utils/wallet"
import WalletStore from "@/store/WalletStore";

// const injected = injectedModule();
// // const coinbaseWallet = coinbaseModule({ darkMode: true });
// const walletConnect = walletConnectModule();
// const torus = torusModule()
// const ledger = ledgerModule()
// // const keepkey = keepkeyModule()
// // const fortmatic = fortmaticModule({ apiKey: FORTMATIC_API_KEY })
// const mew = mewModule()
// // const portis = portisModule({ apiKey: PORTIS_API_KEY })
// const keystone = keystoneModule()
// const dcent = dcentModule()
// const gnosis = gnosisModule()

// State variables
let web3Modal: any; // instance of Blocknative's onboard.js library
const supportedChainIds = [56, 97]; // chain IDs supported by this app
let rawProvider: any
let provider = ref<Web3Provider | JsonRpcProvider>()
let signer = ref<any>(undefined)
let connectStatus = ref<boolean>(false)
let walletAddress = ref<string | undefined>(undefined)
let network = ref<any>(undefined)

// @ts-ignore
export default function useWalletHook() {

  // ------------------------------------------------ Wallet Connection ------------------------------------------------
  /**
   * @notice Initialize the onboard.js module
   */
  // function initializeOnboard() {
  //   onboard = Onboard({
  //     wallets: [injected, walletConnect, ledger, torus, mew, keystone, dcent, gnosis],
  //     chains: [
  //       {
  //         id: '0x1',
  //         token: 'ETH',
  //         label: 'Ethereum Mainnet',
  //         rpcUrl: ETH_RPC_URL,
  //       },
  //       {
  //         id: '0x3',
  //         token: 'tROP',
  //         label: 'Ropsten',
  //         rpcUrl: ROPSTEN_RPC_URL
  //       },
  //       {
  //         id: '0x4',
  //         token: 'rETH',
  //         label: 'Rinkeby',
  //         rpcUrl: RINKEBY_RPC_URL
  //       },
  //       {
  //         id: '0x38',
  //         token: 'BNB',
  //         label: 'BNB Chain',
  //         rpcUrl: 'https://bsc-dataseed.binance.org/'
  //       },
  //       {
  //         id: '0x89',
  //         token: 'MATIC',
  //         label: 'Matic Mainnet',
  //         rpcUrl: 'https://matic-mainnet.chainstacklabs.com'
  //       },
  //       {
  //         id: '0xfa',
  //         token: 'FTM',
  //         label: 'Fantom Mainnet',
  //         rpcUrl: 'https://rpc.ftm.tools/'
  //       }
  //     ],
  //     // i18n: {
  //     //   en: {
  //     //     connect: {
  //     //       selectingWallet: {
  //     //         header: ''
  //     //       }
  //     //     }
  //     //   }
  //     // },
  //     appMetadata: {
  //       name: 'my dapp', // TODO 需要换掉
  //       icon: '<svg><svg/>', // svg string icon // TODO 需要换掉
  //       description: 'Swap tokens for other tokens',
  //       recommendedInjectedWallets: [
  //         {name: 'MetaMask', url: 'https://metamask.io'},
  //         {name: 'Coinbase', url: 'https://wallet.coinbase.com/'},
  //       ],
  //     },
  //     // accountCenter: {
  //     //   desktop: {
  //     //     position: 'topRight',
  //     //     enabled: true,
  //     //     minimal: true,
  //     //   },
  //     //   mobile: {
  //     //     position: 'topRight',
  //     //     enabled: true,
  //     //     minimal: true,
  //     //   }
  //     // },
  //   });
  // }

  // Reset state when, e.g.user switches wallets. Provider/signer are automatically updated by ethers so are not cleared
  function resetState() {
    network.value = undefined
    connectStatus.value = false
    walletAddress.value = undefined

    const walletStore = WalletStore()
    walletStore.$patch((state) => {
      state._provider = undefined
      state._network = undefined
      state._walletAddress = undefined
      state._signer = undefined
    })
  }

  /**
   * @notice Prompt user to connect wallet, or attempt to connect to wallet specified by `name`
   * @param name Wallet name to connect, or undefined to prompt user to select a wallet
   */
  async function connect() {
    console.log('enter connect')
    // If user already connected wallet, return
    // console.log('userAddress:', walletStore.walletAddress)
    // if (walletStore.walletAddress) return

    // if (!web3Modal) initializeOnboard(); // instantiate Onboard instance

    if (!web3Modal) {
      web3Modal = initializeWeb3Model() // instantiate Web3Model instance
    }

    // web3Onboard
    // await web3Modal.connectWallet().then(async () => {
    //   console.log('connect wallet')
    //   const { connectedWallet, wallets } = useOnboard()
    //   console.log('connectingWallet:', connectedWallet.value)
    //   if(wallets.value.length > 0) {
    //     // has already connected
    //     connectStatus.value = true
    //     const provider = wallets.value[0].provider
    //     console.log('provider:', markRaw(provider))
    //     setProvider(provider)
    //     await configureProvider(); // load info based on user's address
    //     // onWalletChange()
    //   } else {
    //     connectStatus.value = false
    //     return
    //   }
    // });

    // Web3Model
    try {
      rawProvider = await web3Modal.connect();
      console.log('rawProvider:', markRaw(rawProvider))
      const _provider = new ethers.providers.Web3Provider(rawProvider);
      provider.value = markRaw(_provider)
      // 连接过钱包的账号
      const accounts = await _provider.listAccounts()
      if (accounts) {
        connectStatus.value = true
        await configureProvider()

        await subscribeProvider(_provider)
      } else {
        connectStatus.value = false
      }
    } catch (error: any) {
      console.log('web3model connect wallet error:', error)
      Dialog(error.message || error)
    }

  }

  async function disconnect() {
    // disconnect the first wallet in the wallets array
    // // web3Onboard
    // const { disconnectConnectedWallet } = useOnboard();
    // await disconnectConnectedWallet();

    // Web3Model
    // web3Modal = initializeWeb3Model()
    // await web3Modal.clearCachedProvider()
    console.log('provider:', provider.value)

    // if(window.ethereum){
    //   window.ethereum.close()
    // }

    resetState()
  }

  const subscribeProvider = async (provider: any) => {
    console.log('subscribeProvider provider:', provider)
    if (!provider.on) {
      return;
    }
    // subscribe to connected account change
    provider.on("accountsChanged", async (accounts: string[]) => {
      console.log('account change')
      console.log('accounts:', accounts[0])
      if (accounts) {
        connectStatus.value = true
        walletAddress.value = accounts[0]
        await connect()
      } else {
        resetState()
      }

      // 重新获取后端的api token
      await startPolling()
      location.reload()
    })
    // subscribe to close
    // // Subscribe to session disconnection
    // window.ethereum.on("disconnect", async (code: number, reason: string) => {
    //   console.log('disconnect change')
    //   console.log(code, reason)
    //   await walletStore.disconnect()
    // })
  }

  // ----------------------------------------------------- Actions -----------------------------------------------------

  // Any actions or data to fetch dependent on user's wallet are done here
  async function configureProvider() {
    // Set network/wallet properties
    console.log('configureProvider rawProvider:', provider.value)
    if (!provider.value) return;
    const _signer = provider.value.getSigner();

    // Get user and network information
    const [_walletAddress, _network] = await Promise.all([
      _signer.getAddress(), // get user's address
      provider.value.getNetwork(), // get information on the connected network
    ]);

    // If nothing has changed, no need to continue configuring
    if (_walletAddress === walletAddress.value && _network.chainId === network.value?.chainId) return;

    // Clear state
    // resetState();

    // Exit if not a valid network
    const chainId = provider.value?.network.chainId; // must be done after the .getNetwork() call
    network.value = markRaw(_network)
    if (!supportedChainIds.includes(chainId)) {
      // add bsc network
      console.log('add bsc network, provider:', provider.value)
      provider.value.send("wallet_addEthereumChain", [{
        chainId: '0x38',
        chainName: 'BNB Smart Chain Mainnet',
        nativeCurrency: {
          name: 'BNB',
          symbol: 'BNB',
          decimals: 18
        },
        rpcUrls: ['https://bsc-dataseed1.binance.org/'],
        blockExplorerUrls: ['https://bscscan.com/']
      }]).then(async (result) => {
        console.log('add bsc network result:', result);
        if (result === null) {
          // 添加成功
          await connect()
        }
      }).catch(err => {
        console.log('add chain error:', err)
      })
    } else {
      // // Get ENS name
      // const userEns = await provider.lookupAddress(walletAddress);

      // Now we save the user's info to the store. We don't do this earlier because the UI is reactive based on these
      // parameters, and we want to ensure this method completed successfully before updating the UI
      signer.value = markRaw(_signer)
      walletAddress.value = _walletAddress
    }

    const walletStore = WalletStore()
    walletStore.$patch((state) => {
      state._provider = provider.value
      state._network = markRaw(_network)
      state._walletAddress = _walletAddress
      state._signer = markRaw(_signer)
      // state._rawProvider = markRaw(rawProvider)
    })


    // Start polling for data
    await startPolling();
  }

  // ---------------------------------------------------- Exports ----------------------------------------------------
  // Define parts of the store to expose. Only expose computed properties or methods to avoid direct mutation of state
  return {
    // Methods
    // configureProvider,
    web3Modal,
    connect,
    disconnect,
    resetState,
    // Properties
    rawProvider,
    isSupportedNetwork: computed(() => (network.value ? supportedChainIds.includes(network.value.chainId) : true)), // assume valid if we have no network information
    network: computed(() => network.value),
    provider: computed(() => provider.value),
    signer: computed(() => signer.value),
    walletAddress: computed(() => walletAddress.value),
    userDisplayName: computed(() => formatAddress(walletAddress.value || '')),
    connectStatus: computed(() => connectStatus.value)
  };
}
